package com.hkemmm.util;

/**
 * 雪花算法 64bit
 * 1位符号位（永正-0）  41位时间戳  10位机器id（5位机器 5位服务）  12位序列号
 */
public class SnowFlake {
    private static SnowFlake snowFlake;

    static {
        snowFlake = new SnowFlake(1, 1);
    }

    //初始开始时间戳
    private static final long INIT_EPOCH = 1543903501000L;

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12;    //序列号占用的位数
    private final static long MACHINE_BIT = 5;      //机器表示占用的位数
    private final static long DATACENTER_BIT = 5;   //数据中心占用的位数

    /**
     * 每一部分最大值，先进行左移运算，在同-1进行异或运算；
     * ^异或：相同位取0，不同位取1
     * 计算机中使用补码运算
     */
    //服务中心最多有31个
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    //每个服务中心机器数量最多有31个
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    //序列号最大值
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    //机器id偏移量
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    //服务id偏移量
    private final static long DATACENTER_LEFT = MACHINE_BIT + SEQUENCE_BIT;
    //时间戳偏移量
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    private static long datacenterId;           //服务中心
    private static long machineId;              //机器标识
    private static long sequence = 0L;          //序列号
    private static long lastStmp = -1L;          //上一次时间戳


    private SnowFlake(long datacenterId, long machineId) {
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }


    public static synchronized long nextId() {
        //当前时间戳
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("发生时钟回拨问题！");
        }

        //在同一毫秒内
        if (currStmp == lastStmp) {

            //序列号递增,超出最大值归0
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0L) {
                //超出序列号最大值，获取下一时间
                currStmp = getNextMill();
            }
        } else {
            //不在同一毫秒内，序列号归零
            sequence = 0L;
        }
        lastStmp = currStmp;
        return (currStmp - INIT_EPOCH) << TIMESTMP_LEFT     //时间戳部分
                | datacenterId << DATACENTER_LEFT           //服务id
                | machineId << MACHINE_LEFT                 //机器id
                | sequence;                                 //序列号

    }

    //获取当前时间戳
    private static long getNewstmp() {
        return System.currentTimeMillis();
    }

    //获取下一毫秒值
    private static long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }

        return mill;
    }
}
